package com.xnx.bincker.leader.core.autorun;

import com.xnx.bincker.leader.domain.Authority;
import com.xnx.bincker.leader.domain.Menu;
import com.xnx.bincker.leader.service.AuthorityService;
import com.xnx.bincker.leader.service.MenuService;
import com.xnx.bincker.leader.utils.ArraysUtils;
import com.xnx.bincker.leader.utils.Pair;
import com.xnx.bincker.leader.utils.StringUtils;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.*;
import java.util.List;

/**
 * 权限扫描器
 * 把@Authority注解的权限和@Menu注解的菜单扫描录入数据库中
 */
@Component
@Log4j2
public class SecurityAnnotationScanner implements CommandLineRunner {
	@Value("${spring.jpa.hibernate.ddl-auto}")
	private String scanType;
	private final MenuService menuService;
	private final AuthorityService authorityService;

	/**
	 * 请求扫描器
	 */
	private final RequestMappingHandlerMapping requestMappingHandlerMapping;

	@Autowired
	public SecurityAnnotationScanner(RequestMappingHandlerMapping requestMappingHandlerMapping, MenuService menuService, AuthorityService authorityService) {
		this.requestMappingHandlerMapping = requestMappingHandlerMapping;
		this.menuService = menuService;
		this.authorityService = authorityService;
	}

	@Override
	public void run(String... args) {
		if(StringUtils.isEmpty(scanType)) return ;
		scanType = scanType.trim().toLowerCase();
		if(scanType.equals("none")) return ;
		switch (scanType){
			case "update":
				update();
				break;
			case "create":
				create();
				break;
			case "create-drop":
				create();
				break;
			case "validate":
				validate();
				break;
			default:
				break;
		}
	}

	@Transactional
	public void update(){
		Pair<List<Menu>, List<Authority>> scanRes = scan();
		List<Menu> menuList = menuService.findAll();
		List<Authority> authorityList = authorityService.findAll();

//		去重操作，结果1为需要新增的，结果2为需要删除的
		Pair<List<Menu>, List<Menu>> menuResult = ArraysUtils.delDuplicate(scanRes.getFirst(), menuList, (a,b)->{
            if(a.equals(b)){
                return 0;
            }
//            需要更新的
            else if(a.getId().equals(b.getId())){
                BeanUtils.copyProperties(a, b);
                return 1;
            }
            return -1;
        });

		log.info("删除菜单: " + menuResult.getSecond());
		menuService.deleteInBatch(menuResult.getSecond());
		log.info("更新菜单: " + menuResult.getFirst());
		menuService.saveAll(menuResult.getFirst());

		Pair<List<Authority>, List<Authority>> authorityResult =
				ArraysUtils.delDuplicate(scanRes.getSecond(), authorityList, (a,b)->{
				    if(a.equals(b)){
				        return 0;
                    }
//                    更新菜单
                    else if(a.getId().equals(b.getId())){
                        BeanUtils.copyProperties(a, b);
                        return 1;
                    }
                    return -1;
                });
		log.info("删除权限: " + authorityResult.getSecond());
		authorityService.deleteInBatch(authorityResult.getSecond());
		log.info("更新权限: " + authorityResult.getFirst());
		authorityService.saveAll(authorityResult.getFirst());
	}

	@Transactional
	public void create(){
		Pair<List<Menu>, List<Authority>> scanRes = scan();
		menuService.deleteAll();
		authorityService.deleteAll();
		menuService.saveAll(scanRes.getFirst());
		authorityService.saveAll(scanRes.getSecond());
	}

	private void validate() {
		Pair<List<Menu>, List<Authority>> scanRes = scan();
		List<Menu> menuList = menuService.findAll();
		List<Authority> authorityList = authorityService.findAll();

		Pair<List<Menu>, List<Menu>> menuResult = ArraysUtils.delDuplicate(scanRes.getFirst(), menuList, (a,b)->{
            if(a.equals(b)){
                return 0;
            }
            else if(a.getId().equals(b.getId())){
                BeanUtils.copyProperties(a, b);
                return 1;
            }
            else{
                return -1;
            }
        });
		Pair<List<Authority>, List<Authority>> authorityResult =
				ArraysUtils.delDuplicate(scanRes.getSecond(), authorityList, (a,b)->a.equals(b)?0:-1);
		menuResult.getFirst().forEach(item->
				log.error(String.format("数据库中不存在(或已更新)菜单 name=%s path=%s", item.getName(), item.getUrl()))
		);
		menuResult.getSecond().forEach(item->
				log.error(String.format("菜单已删除(或已更新) name=%s path=%s", item.getName(), item.getUrl()))
		);
		authorityResult.getFirst().forEach(item->
				log.error(String.format("数据库中不存在(或已更新)权限 path=%s", item.getMappingInfo()))
		);
		authorityResult.getSecond().forEach(item->
				log.error(String.format("权限已删除(或已更新) path=%s", item.getMappingInfo()))
		);
	}

	/**
	 * 扫描权限
	 */
	private Pair<List<Menu>, List<Authority>> scan(){
		Map<RequestMappingInfo, HandlerMethod> mappingInfoHandlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
//		扫描到的菜单
		List<Menu> menuList = new ArrayList<>();
//		扫描到的权限
		List<Authority> authorityList = new ArrayList<>();
//		没有扫描到父级菜单的菜单
		List<Pair<String, Menu>> notFindParentMenu = new ArrayList<>();
//		没有找到父级菜单的权限
		List<Pair<String, Authority>> notFindParentAuthority = new ArrayList<>();
		mappingInfoHandlerMethodMap.forEach((mappingInfo, handlerMethod)->{
//		    当前方法的请求类型，一个方法可有多个请求类型
		    Set<RequestMethod> requestMethods = mappingInfo.getMethodsCondition().getMethods();
//		    当前方法的请求匹配，一个方法可有多个匹配
			Set<String> patterns = mappingInfo.getPatternsCondition().getPatterns();
			Set<MediaType> mediaTypes = mappingInfo.getProducesCondition().getProducibleMediaTypes();

			// 扫描菜单
			Menu menu = null;
			com.xnx.bincker.leader.core.annotation.Menu menuAnnotation =
					handlerMethod.getMethodAnnotation(com.xnx.bincker.leader.core.annotation.Menu.class);
			if(menuAnnotation != null){
				menu = new Menu();
				menu.setId(menuAnnotation.id());
				menu.setName(menuAnnotation.name());
				String url = menuAnnotation.url();
				if(StringUtils.isEmpty(url)){
					url = patterns.iterator().next();
				}
				menu.setUrl(url);
				menu.setDisplay(menuAnnotation.display());
				log.info("scan menu name=" + menu.getName() + "\turl=" + menu.getUrl() + "\tdisplay=" + menu.getDisplay());

				// 查找父级菜单，如果未找到，则先放入list中，扫描其他菜单时再检测
				String parentPath = menuAnnotation.parentMenuUrl();
				if(!parentPath.equals("none")) {
					if (parentPath.isEmpty()) parentPath = getSupPath(menu.getUrl());
					Optional<Menu> parentMenu = findParent(menuList, parentPath);
					if (parentMenu.isPresent()) {
						menu.setParent(parentMenu.get());
					} else {
						notFindParentMenu.add(new Pair<>(parentPath, menu));
					}
				}

				// 判断是否有其他菜单是本菜单的子菜单
				Iterator<Pair<String, Menu>> notFindParentMenuIterator = notFindParentMenu.iterator();
				while (notFindParentMenuIterator.hasNext()){
					Pair<String, Menu> child = notFindParentMenuIterator.next();
					if(menu.getUrl().equals(child.getFirst())){
						child.getSecond().setParent(menu);
						notFindParentMenuIterator.remove();
					}
				}

				// 判断是否有其他权限在本菜单下
				Iterator<Pair<String, Authority>> notFindParentAuthorityIterator = notFindParentAuthority.iterator();
				while (notFindParentAuthorityIterator.hasNext()){
					Pair<String, Authority> child = notFindParentAuthorityIterator.next();
					if(menu.getUrl().equals(child.getFirst())){
						child.getSecond().setParentMenu(menu);
						notFindParentAuthorityIterator.remove();
					}
				}
				menuList.add(menu);
			}

			// 扫描权限
			com.xnx.bincker.leader.core.annotation.Authority authorityAnnotation =
					handlerMethod.getMethodAnnotation(com.xnx.bincker.leader.core.annotation.Authority.class);
			if(authorityAnnotation == null) authorityAnnotation =
					handlerMethod.getBeanType().getAnnotation(com.xnx.bincker.leader.core.annotation.Authority.class);
			if(authorityAnnotation != null){
				Authority authority = new Authority();
				authority.setId(authorityAnnotation.id());
				authority.setRemark(authorityAnnotation.remark());
				Authority.MappingInfo authorityMappingInfo = new Authority.MappingInfo(patterns, requestMethods, mediaTypes);
                authority.setMappingInfo(authorityMappingInfo);
				log.info("scan authority mapping=" + mappingInfo);

//				查找父级菜单，如果没有找到，则加入List中，当扫描到时再设置
				String parentMenuPath = authorityAnnotation.parentMenuUrl();
				if(!parentMenuPath.equals("none")) {
					if (parentMenuPath.isEmpty()) parentMenuPath = getSupPath(authorityMappingInfo.getPath().iterator().next());
					if (menu != null) menu.setAuthority(authority);
					Optional<Menu> parentMenu = findParent(menuList, authorityMappingInfo.getPath().iterator().next());
					if (parentMenu.isPresent()) {
						authority.setParentMenu(parentMenu.get());
					} else {
						notFindParentAuthority.add(new Pair<>(parentMenuPath, authority));
					}
				}
				authorityList.add(authority);
			}
		});
		return new Pair<>(menuList, authorityList);
	}

	/**
	 * 查找父级菜单
	 * @param menuList 菜单列表
	 * @param url 需要查找的地址
	 * @return <pre>Optional<Menu> 父级菜单</pre>
	 */
	private static Optional<Menu> findParent(List<Menu> menuList, String url){
		for (Menu menu : menuList) {
			if(url.equals(menu.getUrl())) return Optional.of(menu);
		}
		return Optional.empty();
	}

	/**
	 * 获取上级路径
	 */
	private static String getSupPath(String path){
		int index = path.lastIndexOf('/');
		if(index == -1) throw new StringIndexOutOfBoundsException("获取上级路径失败 path="+path);
		return path.substring(0, index);
	}
}
